using System;
using System.Collections;
using Commons.Injector.Dtos;
using Commons.Utils;
using Commons.Parser;

namespace Commons.Injector
{
	public class ContextSectionParser : BaseXmlParser<ContextDto>
	{
		public string Path { get; private set; }
		public int NumObjects { get; private set; }
		public int TotalObjects { get; private set; }

		private ContextDto contextDto;

		public ContextSectionParser(string path)
		{
			this.Path = path;
		}

		public void Load()
		{
			LoadFromFileSystem(Path);

			contextDto = Deserialize();

			TotalObjects = contextDto.Objects.Count;

			textReader.Close();
		}

		public IEnumerator Parse()
		{
			if (contextDto == null)
			{
				throw new Exception("Section " + Path + " must be loaded before parse");
			}

			foreach (ObjectDto objectDto in contextDto.Objects)
			{
				Context.Instance.AddDefinition(ParseObject(objectDto));

				NumObjects++;

				yield return null;
			}
		}

		private ObjectDefinition ParseObject(ObjectDto objectDto)
		{
			ObjectDefinition objectDef = new ObjectDefinition();
			objectDef.id = objectDto.Id;
			objectDef.className = objectDto.Class;
			objectDef.factoryClassName = objectDto.FactoryClass;
			objectDef.factoryMethodName = objectDto.FactoryMethod;
			try
			{
				objectDef.factoryMethodGenerics = MapperUtils.GetCommaSeparatedTypes(objectDto.FactoryMethodGenerics);
			}
			catch(ArgumentException e)
			{
				throw new ArgumentException("Error parsing factory method generics of object ID="+objectDto.Id, "FactoryMethodGenerics", e);
			}
			objectDef.singleton = objectDto.Singleton;
			objectDef.initMethodName = objectDto.InitMethod;
			objectDef.scope = objectDto.Scope;

			if (objectDto.Constructor != null)
			{
				objectDef.ctorDef = ParseConstructor(objectDto.Constructor);
			}

			if (objectDto.Properties != null)
			{
				foreach (PropertyDto propertyDto in objectDto.Properties)
				{
					objectDef.properties.Add(propertyDto.Name, ParseProperty(propertyDto, objectDef));
				}
			}

			if (objectDto.Subscriptions != null)
			{
				foreach (SubscriptionDto subscriptionDto in objectDto.Subscriptions)
				{
					objectDef.subscriptions.Add(ParseSubscription(subscriptionDto));
				}
			}

			return objectDef;
		}

		private ListDefinition ParseList(ListDto listDto)
		{
			ListDefinition listDef = new ListDefinition();
			listDef.className = listDto.Type;

			if (listDto.References != null)
			{
				foreach (ReferenceDto referenceDto in listDto.References)
				{
					ListItemDefinition listItemDef = new ListItemDefinition();
					listItemDef.refId = referenceDto.Id;

					listDef.items.Add(listItemDef);
				}
			}

			return listDef;
		}

		private DictionaryDefinition ParseDictionary(DictionaryDto dictionaryDto)
		{
			DictionaryDefinition dictionaryDef = new DictionaryDefinition();

			dictionaryDef.className = dictionaryDto.Type;

			if (dictionaryDto.Entries != null)
			{
				foreach (EntryDto entryDto in dictionaryDto.Entries)
				{
					DictionaryEntryDefinition entryDef = new DictionaryEntryDefinition();
					entryDef.key = entryDto.Key;
					entryDef.refId = entryDto.Ref;
					entryDef.value = entryDto.Value;

					dictionaryDef.entries.Add(entryDef);
				}
			}

			return dictionaryDef;
		}

		private ConstructorDefinition ParseConstructor(ConstructorDto constructorDto)
		{
			ConstructorDefinition constructorDef = new ConstructorDefinition();
			constructorDef.varArgs = constructorDto.VarArgs;

			if (constructorDef.varArgs && constructorDto.Type != null)
			{
				constructorDef.varType = ReflectionHelper.GetCustomType(constructorDto.Type);
			}

			if (constructorDto.Parameters != null)
			{
				foreach (ParameterDto parameterDto in constructorDto.Parameters)
				{
					CtorParamDefinition ctorParamDef = ParseConstructorParam(constructorDef, parameterDto);

					constructorDef.parameters.Add(ctorParamDef.index, ctorParamDef);
				}
			}

			return constructorDef;
		}

		private CtorParamDefinition ParseConstructorParam(ConstructorDefinition constructorDef, ParameterDto parameterDto)
		{
			CtorParamDefinition ctorParamDef = new CtorParamDefinition();
			ctorParamDef.refId = parameterDto.Ref;
			ctorParamDef.val = parameterDto.Value;
			ctorParamDef.index = parameterDto.Index;
			ctorParamDef.lazy = parameterDto.Lazy;

			if (parameterDto.Type != null)
			{
				ctorParamDef.type = ReflectionHelper.GetCustomType(parameterDto.Type);
			}

			if (ctorParamDef.type == null)
			{
				if (constructorDef.varArgs && constructorDef.varType != null)
				{
					ctorParamDef.type = constructorDef.varType;
				}
			}

			return ctorParamDef;
		}

		private PropertyDefinition ParseProperty(PropertyDto propertyDto, ObjectDefinition parentDef)
		{
			PropertyDefinition propertyDef = new PropertyDefinition();
			propertyDef.name = propertyDto.Name;
			propertyDef.lazy = propertyDto.Lazy;

			if (propertyDto.Dictionary != null)
			{
				DictionaryDefinition dictionaryDef = ParseDictionary(propertyDto.Dictionary);
				dictionaryDef.id = parentDef.id + "_" + propertyDef.name + "_dictionary";
				dictionaryDef.scope = parentDef.scope;

				propertyDef.refId = dictionaryDef.id;

				Context.Instance.AddDefinition(dictionaryDef);
			}
			else if (propertyDto.List != null)
			{
				ListDefinition listDef = ParseList(propertyDto.List);
				listDef.id = parentDef.id + "_" + propertyDef.name + "_list";
				listDef.scope = parentDef.scope;

				propertyDef.refId = listDef.id;

				Context.Instance.AddDefinition(listDef);
			}
			else
			{
				propertyDef.refId = propertyDto.Ref;
				propertyDef.val = propertyDto.Value;

				if (propertyDto.Type != null)
				{
					propertyDef.type = ReflectionHelper.GetCustomType(propertyDto.Type);
				}
			}

			return propertyDef;
		}

		private SubscriptionDefinition ParseSubscription(SubscriptionDto subscriptionDto)
		{
			SubscriptionDefinition subscriptionDef = new SubscriptionDefinition();
			subscriptionDef.handler = subscriptionDto.Handler;
			subscriptionDef.argsType = ReflectionHelper.GetCustomType(subscriptionDto.Args);
			if (subscriptionDef.argsType == null && !string.IsNullOrEmpty(subscriptionDto.Args))
			{
				throw new ArgumentException("Cannot get arg type "+subscriptionDto.Args, "args");
			}

			if (subscriptionDto.Publishers != null)
			{
				foreach (PublisherDto publisherDto in subscriptionDto.Publishers)
				{
					PublisherDefinition publisherDef = new PublisherDefinition();
					publisherDef.refId = publisherDto.Ref;
					publisherDef.eventName = publisherDto.Event;

					subscriptionDef.publishers.Add(publisherDef);
				}
			}

			return subscriptionDef;
		}
	}
}
